package spectra.ee.web.common.controller;

import java.io.*;
import java.net.*;
import java.util.ArrayList;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.io.IOUtils;

import spectra.base.BasePublic;
import spectra.base.log.LP;
import spectra.base.log.Logger;
import spectra.base.licensemanager.Decrypt;
import spectra.base.util.FileUtil;
import spectra.base.util.StringUtil;
import spectra.base.util.Util;
import spectra.base.util.http.HttpTimeoutHandler;
import spectra.base.web.download.ContentRange;
import spectra.base.web.download.LimitedBandwidthInputStream;
import spectra.base.web.download.RandomAccessFileBufferedInputStream;
import spectra.base.web.download.SeekableInputStream;
import spectra.ee.EEWebConfig;
import spectra.ee.web.WebConfig;
import spectra.ee.web.WebPublic;

/**
* 지정된 실제 파일명과 디렉토리, 그리고 주어진 원본 파일명을 이용해<br>
* 파일을 다운로드할 수 있도록 한다.
*
* @author kspark
*/
public class DownLoadBaseServlet extends HttpServlet
{
    /** Logger 객체. */
    private static Logger logger = Logger.getLoggerInstance(DownLoadBaseServlet.class.getName());

    /** 디버깅여부. */
    protected boolean isDebug = logger.isDebugEnabled();

    /**
    * <pre>
    * Parameter 는 fileInfo 하나이며 암호화된 값을 받는다.
    * 암호화된 값은 배열형태로 리턴된다.(FILEINFO 배열)
    * FILEINFO[0] : 다운로드할 실제 디렉토리로 부터 파일 이름까지 Full Path.
    * FILEINFO[1] : 다운로드시 보여줄 파일명.
    * </pre>
    *
    * @param req HttpServletRequest 객체.
    * @param res HttpServletResponse 객체.
    *
    * @throws IOException Signals that an I/O exception has occurred.
    * @throws ServletException the servlet exception
    */
    public void service(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException
    {
        req.setCharacterEncoding("UTF-8");
        String strFileInfo = req.getParameter("fileInfo");

        if(strFileInfo == null)
        {
            errorPrintln(res, null);
            return;
        }
        else
        {
            String [] aFileInfo = Decrypt.decrypt(strFileInfo, EEWebConfig.getEncryptKey(), BasePublic.ENCODING);


            if( isDebug && aFileInfo != null )
                for(int i=0; i < aFileInfo.length; i++ )
                {
                    debugPrint("FILEINFO["+i+"]=" + aFileInfo[i]);
                }

            if( aFileInfo != null && aFileInfo.length >= 2 )
            {
                String strServerStructure = WebConfig.getEnvValue(WebPublic.COM_SERVER_STRUCTURE_TYPE);

                if( WebPublic.COM_SERVER_STRUCTURE_TYPE_DIV.equals(strServerStructure) )
                {
                    // 첨부파일을 EE 서버로 부터 받아서 전달
                    writeFileByAttachServer(req, res, aFileInfo);
                }
                else
                {
                    // 첨부파일을 로컬 디렉토리에서 읽어들여 전달
                    writeFile(req, res, aFileInfo);
                }
            }
            else
            {
                errorPrintln(res, null);
            }
        }
    }

    /**
     * 첨부파일을 로컬 디렉토리에서 읽어들여 전달.<br>
     * (Write file)
     *
     * @param req HttpServletRequest 객체.
     * @param res HttpServletResponse 객체.
     * @param aFileInfo 첨부파일정보를 담은 배열.
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws ServletException the servlet exception
     */
    protected void writeFile(HttpServletRequest req, HttpServletResponse res, String [] aFileInfo)
            throws IOException, ServletException
    {

        String strAttachDir = WebConfig.getEnvValue(WebPublic.COM_ATTACH_DIR);  // 도메인 별로 첨부파일 경로가 다름!
        // 해당 도메인의 첨부파일 경로가 이상할 경우..
        if( StringUtil.isBlank(strAttachDir) )
        {
            strAttachDir = WebConfig.getConfigValue(WebPublic.CFG_SVC_COMMON, WebPublic.DIR_DATAFILE_HOME); // 첨부파일 업로드 경로
        }

        boolean isAttach = Boolean.valueOf((StringUtil.defaultString(req.getParameter("isAttach"), "true"))).booleanValue();
        String strFileName = URLEncoder.encode(aFileInfo[1], "UTF-8");

        if(isAttach)
        {
        	res.setContentType("application/octet-stream; charset=utf-8");
        }
        else
        {
        	res.setContentType(getMimeTypeFromFileName(strFileName));
        }
        
        if (strAttachDir != null && strAttachDir.length() > 0)
        {
            aFileInfo[0] = strAttachDir + aFileInfo[0];
        }

        String strHeader = "";
        String userAgent = req.getHeader("User-Agent");
        debugPrint("Client ::::::::::::::: " + userAgent);
        debugPrint(aFileInfo[0]);
        if (userAgent.indexOf("MSIE 5.5") > -1)  // MS IE 5.5 이하
        {
            strFileName = URLEncoder.encode(aFileInfo[1].replaceAll(" ", ""), "UTF-8");
            strHeader = "filename=\"" + strFileName + "\";";
        }
        else if (userAgent.indexOf("MSIE") > -1)  // MS IE (보통은 6.x 이상 가정)
        {
            strFileName = URLEncoder.encode(aFileInfo[1].replaceAll(" ", ""), "UTF-8");
            strHeader = "attachment; filename=\"" + strFileName + "\";";
        }
        else  // 모질라나 오페라
        {
            strFileName = new String(aFileInfo[1].getBytes("UTF-8"), "latin1");
            strHeader = "attachment; filename=\"" + strFileName + "\";";
        }

        debugPrint("strHeader ::::::::: " + strHeader);
        if( isAttach )
            res.setHeader("Content-Disposition", strHeader);

        byte[] b = new byte[1024];

        //iphone 에서 다운로드를 지원하기 위해 부분 다운로드 지원
        String rangeHeader = req.getHeader("range"); 
        //ArrayList ranges = parseRange(rangeHeader);
        
        File fDownloadFile = new File(aFileInfo[0]);        
        if (fDownloadFile.isFile())
        {
        	//Content-length 헤더 셋팅
        	SeekableInputStream baseIn = new RandomAccessFileBufferedInputStream(fDownloadFile);
            InputStream is = baseIn;
            
            try
            {
            	if (rangeHeader == null )
            	{
            		res.setContentLength((int)fDownloadFile.length());
            	}
            	else
            	{
            		res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            		//Range: bytes=0-1  이거나 Range: bytes=0-65919 인 경우 또는 466944-475491
            		ContentRange r = ContentRange.parseRange(rangeHeader, fDownloadFile.length());
            		//헤더 셋팅
            		res.setHeader("Accept-Ranges", "bytes");
            		res.setHeader("ETag","W/\"" + Util.getMD5Checksum(fDownloadFile.getAbsolutePath()) + "\"");
            		res.setHeader("Content-Range", r.toHeaderValue());
            		res.setContentLength((int)r.getContentLength());
            		
            		is = r.limitedInputStream(baseIn);
            		
            	}
            	IOUtils.copy(limitBandwidth(is), res.getOutputStream());
                //is.close();
                //outs.flush();
                //outs.close();
                
            }
            catch (Exception e)
            {
                //errorPrintln(res, null);
            	System.out.println(Thread.currentThread().getName() + " : " + e);
                logger.error(LP.EXCEPTION, e);
            }
            finally
            {
                /*
            	if (ins != null)
                {
                    ins.close();
                }
                */
            	IOUtils.closeQuietly(is);
            }
        }
        else
        {
            errorPrintln(res, null);
        }
    }

    private InputStream limitBandwidth(InputStream in) {
        long bandwidthLimit = 2 * 1024 * 1024L;
        if (bandwidthLimit > 0) {
            return new LimitedBandwidthInputStream(in, bandwidthLimit, 5000000L);
        }
        return in;
    }
    /**
     * 첨부파일을 EE 서버로 부터 받아서 전달<br>
     * (Write file by attach server.)
     *
     * @param req HttpServletRequest 객체.
     * @param res HttpServletResponse 객체.
     * @param aFileInfo 첨부파일정보를 담은 배열.
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws ServletException the servlet exception
     */
    protected void writeFileByAttachServer(HttpServletRequest req, HttpServletResponse res, String [] aFileInfo)
        throws IOException, ServletException
    {
        String strAttachUrl = WebConfig.getConfigValue(WebPublic.CFG_SVC_COMMON, WebPublic.URL_WEBAPPS) +
                WebConfig.getConfigValue(WebPublic.CFG_SVC_COMMON, WebPublic.ATTACH_DOWNLOAD_URL); // 디폴트 첨부파일 다운로드 URL

        String strDomainId = WebConfig.getDomainId();
        String strFileName = URLEncoder.encode(aFileInfo[1], "UTF-8");
        
        // mod. hschoi 20120821
        // 상담지식 미리보기(파일)시 파일명이 정상적으로 보이지 않음 수정
        
        String strRealFileName = StringUtil.defaultString(req.getParameter("fileName"), "");
        String [] arrFileName = null;
        if(!"".equals(strRealFileName))
        {
        	arrFileName = Decrypt.decrypt(strRealFileName, EEWebConfig.getEncryptKey(), BasePublic.ENCODING);
        	strRealFileName = arrFileName[0];
        	strFileName = strRealFileName;
        }	
        strAttachUrl += "?domainId=" + strDomainId + "&pathname=" + aFileInfo[0] + "&filename=" + strFileName;
        debugPrint(strAttachUrl);
        
        boolean isAttach = Boolean.valueOf((StringUtil.defaultString(req.getParameter("isAttach"), "true"))).booleanValue();

        if(isAttach)
        {
        	res.setContentType("application/octet-stream; charset=utf-8");
        }
        else
        {
        	res.setContentType(getMimeTypeFromFileName(strFileName));
        }
            

        URL url = null;
        URLConnection conn = null;
        HttpURLConnection httpConn = null;
        InputStream is = null;
        byte[] b = new byte[1024];
        BufferedInputStream ins = null;
        BufferedOutputStream outs = null;
        try
        {
            url = new URL((URL) null, strAttachUrl, new HttpTimeoutHandler(1800000)); // 3분의 타임아웃

            conn = url.openConnection();
            httpConn = (HttpURLConnection)conn;
            httpConn.setRequestMethod("POST");
            httpConn.setUseCaches(false); // URL 연결의 캐시 사용여부를 설정. (default=true)
            is = httpConn.getInputStream();

            String strHeader = "";
            String userAgent = req.getHeader("User-Agent");
            //iphone 에서 호출되는 경우 range 지원하기 위해
            String rangeHeader = req.getHeader("range"); 
            
            debugPrint("Client ::::::::::::::: " + userAgent);
            debugPrint("Range ::::::::::::::: " + rangeHeader);
            debugPrint(aFileInfo[0]);
            
            // mod. hschoi 20120821
            // 상담지식 미리보기(파일)시 파일명이 정상적으로 보이지 않음 수정
            if (userAgent.indexOf("MSIE 5.5") > -1)  // MS IE 5.5 이하
            {
                strFileName = URLEncoder.encode(aFileInfo[1].replaceAll(" ", ""), "UTF-8");
                if(!"".equals(strRealFileName))
                	strFileName = URLEncoder.encode(strRealFileName.replaceAll(" ",""), "UTF-8");
                strHeader = "filename=\"" + strFileName + "\";";
            }
            else if (userAgent.indexOf("MSIE") > -1)  // MS IE (보통은 6.x 이상 가정)
            {
                strFileName = URLEncoder.encode(aFileInfo[1].replaceAll(" ", ""), "UTF-8");
                if(!"".equals(strRealFileName))
                	strFileName = URLEncoder.encode(strRealFileName.replaceAll(" ",""), "UTF-8");
                strHeader = "attachment; filename=\"" + strFileName + "\";";
            }
            else  // 모질라나 오페라
            {
                strFileName = new String(aFileInfo[1].getBytes("UTF-8"), "latin1");
                if(!"".equals(strRealFileName))
                	strFileName = new String(strRealFileName.getBytes("UTF-8"),"latin1");
                strHeader = "attachment; filename=\"" + strFileName + "\";";
            }

            debugPrint("strHeader ::::::::: " + strHeader);
            if( isAttach )
                res.setHeader("Content-Disposition", strHeader);

            ins = new BufferedInputStream(is);
            outs = new BufferedOutputStream(res.getOutputStream());
            int read = 0;
            while ((read = ins.read(b)) != -1)
            {
                outs.write(b,0,read);
            }
            outs.flush();
        }
        catch(InterruptedIOException e)  // 타임 아웃
        {
            logger.error("WebUtil.getResponseContent - Timeout ... : " + strAttachUrl, e);
        }
        catch(MalformedURLException e)
        {
            logger.error("WebUtil.getResponseContent - Invaild Request URL ... : " + strAttachUrl, e);
        }
        catch(ProtocolException e)
        {
            logger.error("WebUtil.getResponseContent - Invaild Request Protocol ... : " + strAttachUrl, e);
        }
        catch(Exception e)
        {
            logger.error("WebUtil.getResponseContent - I/O Error ... : " + strAttachUrl, e);
        }
        finally
        {
            try
            {
                if(is != null)
                    is.close();
            }
            catch(Exception e)
            {
            }
            try
            {
                if(ins != null)
                    ins.close();
            }
            catch(Exception e)
            {
            }
            try
            {
                if(outs != null)
                    outs.close();
            }
            catch(Exception e)
            {
            }
        }
    }

    /**
     * 주어진 에러페이지로 이동한다.<br/>
     * 에러페이지가 null 이거나 비어 있으면 에러페이지를 만들어 보낸다.
     *
     * @param res HttpServletResponse 객체.
     * @param strErrorPage 에러페이지 URL.
     *
     * @throws IOException Signals that an I/O exception has occurred.
     * @throws ServletException the servlet exception
     */
    protected void errorPrintln(HttpServletResponse res, String strErrorPage)
            throws IOException, ServletException
    {
        if (strErrorPage == null || strErrorPage.length() == 0)
        {
            PrintWriter out = res.getWriter();
            res.setContentType("text/html");
            out.println("<html><head><title></title></head><body>");
            out.println("There is no file !!");
            out.println("</body></html>");
            out.close();
        }
        else
        {
            res.sendRedirect(strErrorPage);
        }
    }
    /**
     * 파일 확장자를 검사해서  ShockWaveFile인지 여부를 리턴한다.
     * @param fileName 파일명
     * @return ShockWaveFile 여부
     */
    protected boolean isShockWaveFile(String fileName)
    {
        if( fileName == null )
            return false;

        return "swf".equals(FileUtil.getFileExt(fileName));
    }
    
    protected String getMimeTypeFromFileName(String fileName)
    {
    	String strMimeType = "application/octet-stream; charset=utf-8";
    	String strFileExt = FileUtil.getFileExt(fileName).toLowerCase();
    	System.out.println(strFileExt);
    	if (isShockWaveFile(fileName))
    	{
    		strMimeType = "application/x-shockwave-flash";
    	}
        else if (strFileExt.equalsIgnoreCase("aif"))
        {
        	strMimeType = "audio/x-aiff";
        }
        else if (strFileExt.equalsIgnoreCase("jpg"))
        {
        	strMimeType = "image/jpeg";
        }
        else if (strFileExt.equalsIgnoreCase("mpg"))
        {
        	strMimeType = "video/mpeg";
        }
        else if (strFileExt.equalsIgnoreCase("m4a"))
        {
        	strMimeType = "audio/mpeg";
        }
        else if (strFileExt.equalsIgnoreCase("mp3"))
        {
        	strMimeType = "audio/mpeg";
        }
        else if (strFileExt.equalsIgnoreCase("wav"))
        {
        	strMimeType = "audio/x-wav";
        }
        else if (strFileExt.equalsIgnoreCase("png"))
        {
        	strMimeType = "image/png";
        }
        else if (strFileExt.equalsIgnoreCase("mov"))
        {
        	strMimeType = "video/quicktime";
        }
    	
    	return strMimeType;
    }
    /*
    private ArrayList parseRange(String rangeHeader) 
    { 
        ArrayList ranges = null; 
         
        if (rangeHeader != null && rangeHeader.startsWith("bytes")) 
        {             
            ranges = new ArrayList(8);
            rangeHeader = rangeHeader.substring(6);
            String[] rangesComma = rangeHeader.split(","); 
            String r = "";
            
            for (int i = 0; i < rangesComma.length; i++) 
            { 
                r = rangesComma[i];
            	int dashPos = r.indexOf('-'); 
                long end = 0; 
                long start = Long.parseLong(r.substring(0, dashPos)); 
                if (dashPos < r.length() - 1) 
                { 
                    end = Long.parseLong(r.substring(dashPos + 1, r.length())); 
                } 

                ranges.add(new ByteRange(start, end)); 
            } 
        } 
        return ranges; 
    } 
     */

    /**
     * 콘솔에 디버깅 메세지 출력
     *
     * @param msg 출력메세지.
     */
    protected void debugPrint(String msg)
    {
        if(isDebug)
            System.out.println(msg);
    }
}
